package io.cici.cc.mybatis.lite.binding;

import io.cici.cc.mybatis.lite.mapping.MappedStatement;
import io.cici.cc.mybatis.lite.reflection.MetaDataObject;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.session.Session;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.List;

@Slf4j
@Setter
@Getter
public class MapperMethod {

    private final SqlCommand command;
    private final MethodReturnTypeSignature methodReturnTypeSignature;

    public MapperMethod(Configuration configuration, Class<?> mapperInterface, Method method) {
        this.command = new SqlCommand(configuration, mapperInterface, method);
        this.methodReturnTypeSignature = new MethodReturnTypeSignature(configuration, mapperInterface, method);
    }

    public Object execute(Session session, Object[] args) {
        Object result;
        switch (command.getSqlCommandType()) {
            case INSERT: {
                Object param = methodReturnTypeSignature.convertArgsToSqlCommandParam(args);
                result = rowCountResult(session.insert(command.getId(), param));
                break;
            }
            case UPDATE: {
                Object param = methodReturnTypeSignature.convertArgsToSqlCommandParam(args);
                result = rowCountResult(session.update(command.getId(), param));
                break;
            }
            case DELETE: {
                Object param = methodReturnTypeSignature.convertArgsToSqlCommandParam(args);
                result = rowCountResult(session.delete(command.getId(), param));
                break;
            }
            case SELECT:
                if (methodReturnTypeSignature.isReturnsVoid() && methodReturnTypeSignature.hasResultHandler()) {
                    executeWithResultHandler(session, args);
                    result = null;
                } else if (methodReturnTypeSignature.isReturnsMany()) {
                    result = executeForMany(session, args);
                } else {
                    Object param = methodReturnTypeSignature.convertArgsToSqlCommandParam(args);
                    result = session.selectOne(command.getId(), param);

                }
                break;
/*            case FLUSH:
                result = sqlSession.flushStatements();
                break;*/
            default:
                throw new BindingException("Unknown execution method for: " + command.getId());
        }
        if (result == null && methodReturnTypeSignature.getReturnType().isPrimitive() && !methodReturnTypeSignature.isReturnsVoid()) {
            throw new BindingException("Mapper method '" + command.getId() + "' attempted to return null from a method with a primitive return type (" + methodReturnTypeSignature.getReturnType() + ").");
        }
        return result;
    }

    private Object rowCountResult(int rowCount) {
        final Object result;
        if (methodReturnTypeSignature.isReturnsVoid()) {
            result = null;
        } else if (Integer.class.equals(methodReturnTypeSignature.getReturnType()) || Integer.TYPE.equals(methodReturnTypeSignature.getReturnType())) {
            result = rowCount;
        } else if (Long.class.equals(methodReturnTypeSignature.getReturnType()) || Long.TYPE.equals(methodReturnTypeSignature.getReturnType())) {
            result = (long) rowCount;
        } else if (Boolean.class.equals(methodReturnTypeSignature.getReturnType()) || Boolean.TYPE.equals(methodReturnTypeSignature.getReturnType())) {
            result = rowCount > 0;
        } else {
            throw new BindingException("Mapper method '" + command.getId() + "' has an unsupported return type: " + methodReturnTypeSignature.getReturnType());
        }
        return result;
    }

    private void executeWithResultHandler(Session session, Object[] args) {
        MappedStatement mappedStatement = session.getConfiguration().getMappedStatement(command.getId());
        if (void.class.equals(mappedStatement.getResultMappings().getType())) {
            throw new BindingException(
                    "method " + command.getId() + " needs either a @ResultMap annotation, a @ResultType annotation,"
                            + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
        }
        Object parameter = methodReturnTypeSignature.convertArgsToSqlCommandParam(args);

        session.select(command.getId(), parameter, methodReturnTypeSignature.extractResultHandler(args));

    }

    private <E> Object executeForMany(Session session, Object[] args) {

        Object parameter = methodReturnTypeSignature.convertArgsToSqlCommandParam(args);
        List<E> list = session.selectList(command.getId(), parameter);
        if (!methodReturnTypeSignature.getReturnType().isAssignableFrom(list.getClass())) {
            return convertToDeclaredCollection(session.getConfiguration(), list);
        }
        return list;
    }

    private <E> Object convertToDeclaredCollection(Configuration configuration, List<E> list) {
        Object collection = Configuration.objectFactory.create(methodReturnTypeSignature.getReturnType());
        MetaDataObject metaDataObject = configuration.newMetaDataObject(collection);
        metaDataObject.addAll(list);
        return collection;
    }


}
